/**
 * Copyright 2009 Erick Armbrust 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

package tapioca.dt;

import tapioca.testing.AppEngineTestCase;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;

/**
 * Tests for the {@link ShadowEntity} class.
 * 
 * @author armbrust@gmail.com (Erick Armbrust)
 */
public class ShadowEntityTest extends AppEngineTestCase {  
  
  ///// Helper functions /////
  
  private void getAndValidateShadow(Key key) {
    try {
      Entity shadow = ds.get(key);
      assertTrue(shadow.hasProperty(ShadowEntity.DT_KEY_PROP));
      assertTrue(shadow.hasProperty(ShadowEntity.ACTION_TYPE_PROP));
      assertTrue(shadow.hasProperty(ShadowEntity.SHADOW_BLOB_PROP) ||
          shadow.hasProperty(ShadowEntity.DELETE_KEY_PROP));
    } catch (EntityNotFoundException e) {
      fail("Shadow entity not saved to datastore.");
    }
  }
  
  private void validateShadowBlob(ShadowEntity se, Entity entity) {
    Blob blob = (Blob) se.getEntity().getProperty(ShadowEntity.SHADOW_BLOB_PROP);
    EntityProto proto = new EntityProto();
    proto.parseFrom(blob.getBytes());
    Entity shadowed = EntityTranslator.createFromPb(proto);
    assertEquals(entity.getProperties(), shadowed.getProperties());
  }
  
  ///// Tests /////
  
  /**
   * Test that creating a put shadow works for the various create/update situations.
   */
  public void testCreateAndSavePutShadow() {
    // Test creating a shadow for an entity that has not been assigned an id and does not have a 
    // named key fails.
    Entity f1 = createDtEntity("foo", "bar");
    try {
      ShadowEntity.createAndSavePutShadow(ds, dummyVersion, f1);
      fail("Trying to create an entity with an incomplete key and no parent should fail.");
    } catch (IllegalArgumentException e) { /* Good */ }
    
    // Test that creating a named entity does the right thing.
    Entity f2 = createDtNamedEntity("foo", "bar", "baz");
    ShadowEntity se = ShadowEntity.createAndSavePutShadow(ds, dummyVersion, f2);
    getAndValidateShadow(se.getEntity().getKey());
    validateShadowBlob(se, f2);
    assertEquals(dummyVersion, se.getDtKey());
    assertEquals(dummyVersion, se.getEntity().getProperty(ShadowEntity.DT_KEY_PROP));
    assertEquals(f2.getProperties(), se.getShadowed().getProperties());
    assertNull(se.getEntity().getProperty(ShadowEntity.DELETE_KEY_PROP));
    assertNull(se.getDeleteKey());
    assertEquals(ActionType.PUT, se.getActionType());
    assertEquals(ActionType.PUT.toString(), 
        se.getEntity().getProperty(ShadowEntity.ACTION_TYPE_PROP));
    assertEquals(f2.getKey(), se.getEntity().getKey().getParent());
    
    // Test that updating an entity does the right thing.
    Entity f4 = createAndSaveDtEntity("foo", "bar");
    se = ShadowEntity.createAndSavePutShadow(ds, dummyVersion, f4);
    getAndValidateShadow(se.getEntity().getKey());
    validateShadowBlob(se, f4);
    assertEquals(dummyVersion, se.getDtKey());
    assertEquals(dummyVersion, se.getEntity().getProperty(ShadowEntity.DT_KEY_PROP));
    assertEquals(f4.getProperties(), se.getShadowed().getProperties());
    assertNull(se.getEntity().getProperty(ShadowEntity.DELETE_KEY_PROP));
    assertNull(se.getDeleteKey());
    assertEquals(ActionType.PUT, se.getActionType());
    assertEquals(ActionType.PUT.toString(), 
        se.getEntity().getProperty(ShadowEntity.ACTION_TYPE_PROP));
    assertEquals(f4.getKey(), se.getEntity().getKey().getParent());
  }
  
  /**
   * Test that creating a delete shadow works properly.
   */
  public void testCreateAndSaveDeleteShadow() {
    // Test that we cannot delete an entity with an incomplete key.
    Entity f1 = createDtEntity("foo", "bar");
    try {
      ShadowEntity.createAndSaveDeleteShadow(ds, dummyVersion, f1.getKey());
      fail("Should not allow delete shadow creation for incomplete keys.");
    } catch (IllegalArgumentException e) {
      // Good.
    }
    
    // Test that deleting an entity does the right thing.
    Entity f2 = createAndSaveDtEntity("foo", "bar");
    ShadowEntity se = ShadowEntity.createAndSaveDeleteShadow(ds, dummyVersion, f2.getKey());
    getAndValidateShadow(se.getEntity().getKey());
    assertEquals(dummyVersion, se.getDtKey());
    assertEquals(dummyVersion, se.getEntity().getProperty(ShadowEntity.DT_KEY_PROP));
    assertNull(se.getEntity().getProperty(ShadowEntity.SHADOW_BLOB_PROP));
    assertNull(se.getShadowed());
    assertEquals(f2.getKey(), se.getDeleteKey());
    assertEquals(f2.getKey(), se.getEntity().getProperty(ShadowEntity.DELETE_KEY_PROP));
    assertEquals(ActionType.DELETE, se.getActionType());
    assertEquals(ActionType.DELETE.toString(), 
        se.getEntity().getProperty(ShadowEntity.ACTION_TYPE_PROP));    
    assertEquals(f2.getKey(), se.getEntity().getKey().getParent());
  }
  
  /**
   * Test that loading a shadow object works properly.
   */
  public void testLoadShadow() throws Exception {
    Entity f1 = createAndSaveDtEntity("foo", "bar");
    ShadowEntity se = ShadowEntity.createAndSavePutShadow(ds, dummyVersion, f1);
    Transaction txn = ds.beginTransaction();
    ShadowEntity seLoaded = ShadowEntity.loadShadow(ds, txn, se.getEntity().getKey());
    txn.commit();
    
    assertEquals(se.getEntity(), seLoaded.getEntity());
    assertEquals(se.getShadowed(), seLoaded.getShadowed());
    assertEquals(se.getDtKey(), seLoaded.getDtKey());
    assertEquals(se.getDeleteKey(), seLoaded.getDeleteKey());
    assertEquals(se.getActionType(), seLoaded.getActionType());
    
    se = ShadowEntity.createAndSaveDeleteShadow(ds, dummyVersion, f1.getKey());
    txn = ds.beginTransaction();
    seLoaded = ShadowEntity.loadShadow(ds, txn, se.getEntity().getKey());
    txn.commit();
    
    assertEquals(se.getEntity(), seLoaded.getEntity());
    assertEquals(se.getShadowed(), seLoaded.getShadowed());
    assertEquals(se.getDtKey(), seLoaded.getDtKey());
    assertEquals(se.getDeleteKey(), seLoaded.getDeleteKey());
    assertEquals(se.getActionType(), seLoaded.getActionType());
  }
  
}
